package pacman;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import maze.Barcode;
import maze.Coordinate;
import maze.Location;
import maze.Maze;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;
import communication.Config;

/**
 * A class representing a game of pacman with 4 ghosts and 1 pacman
 */
public class Game {
	
	/**
	 * The players of this game controlling the ghosts
	 */
	private Ghost[] ghosts;
	
	/**
	 * The pacman the players will chase
	 */
	private Pacman pacman;
	
	/**
	 * Indicates the number of JOIN commands received
	 */
	private int numberOfJoins;
	
	/**
	 * The number of players that have registerd their name
	 */
	private int numberOfNamesRegistered;
	
	/**
	 * The last locations where Pacman was spotted.
	 */
	private ArrayList<Location> lastPacmanLocations;
	
	/**
	 * The channel used for communicating
	 */
	private Channel channel;
	
	private QueueingConsumer consumer;
	 
	private CommandParser parser;
	
	private Timer timer;
	
	public Game() throws IOException {
		setGhosts(new Ghost[4]);
		setPacman(new Pacman());
		setNumberOfJoins(0);	
		setNumberOfNamesRegistered(0);
		
		ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(Config.HOST_NAME);
        Connection connection = factory.newConnection();
        setChannel(connection.createChannel());

        getChannel().exchangeDeclare(Config.EXCHANGE_NAME, "fanout");
        String queueName = getChannel().queueDeclare().getQueue();
        getChannel().queueBind(queueName, Config.EXCHANGE_NAME, "");

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
        
        setConsumer(new QueueingConsumer(getChannel()));
        
        this.channel.basicConsume(queueName, true, getConsumer());
        
        setParser(new CommandParser(this));
		this.setTimer(new Timer());
		
		ReceiveTask receiveCommand = new ReceiveTask();
		//Om de 100ms alle ingekomen messages verwerken
		getTimer().schedule(receiveCommand, 0, 500);
	}
	
	private class ReceiveTask extends TimerTask{
		
		public ReceiveTask(){
		}
		
		@Override
		public void run() {
			try {
				QueueingConsumer.Delivery delivery = getConsumer().nextDelivery();
	            String message = new String(delivery.getBody());
			    System.out.println("Message: " + message);
			    getParser().parseCommand(message); 
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	/**
	 * Processes the JOIN command
	 */
	public void processJOIN(){
		setNumberOfJoins(getNumberOfJoins()+1);
	}
		
	/**
	 * Processes the NAME command
	 * @throws IOException 
	 */
	public void processNAME(String name, String Version) throws IOException{
		if(getGhostWithName(name) != null){
			resetNames();
		}
		//Nieuwe speler toevoegen
		//Deze start op locatie 0,0 in zijn eigen maze
		//TODO:
		getGhosts()[getNumberOfNamesRegistered()] = new Ghost(new Location(0,0), 0, "1.0");
		setNumberOfNamesRegistered(getNumberOfNamesRegistered()+1);
	}
	
	/**
	 * Processes the POSITION command
	 */
	public void processPOSITION(String name, int x, int y){
		getGhostWithName(name).processPOSITION(new Location(x, y));
	}
	

	/**
	 * Processes the DISCOVER command
	 */
	public void processDISCOVER(String name, int x, int y, int north, int east, int south, int west){
		Ghost ghost = getGhostWithName(name);
		for(int i=0; i<ghosts.length; i++){
			Ghost receivingGhost = ghosts[i];
			if(ghost != receivingGhost){
				Barcode barcodeRec = controlBarcode(ghosts[i], ghost);
				if(barcodeRec != null){
					Barcode barcodeSend = ghost.getMaze().getBarcodeOnString(barcodeRec.getCode());
					//processDISCOVER(new Location(x,y), north, east, south, west);
					//TODO relatieve positie berekenen van de barcode in andere maze en daar muren tekenen
				}
				
			}
		}

		//TODO als de absolute maze gekend is deze ook updaten
	}
	/**
	 * controleer of er 2 robots zijn met dezelfde barcode.
	 * @param receiver
	 * @param sender
	 * @return 	null als er geen dezelfde barcodes zijn
	 * 			barcode als er dezelfde barcode is.
	 */
	private Barcode controlBarcode(Ghost receiver, Ghost sender){
		for(int i=0; i< sender.getBarcodesRead().size(); i++){
			String barcodeSender = sender.getBarcodesRead().get(i);
			for(int j=0; i < receiver.getBarcodesRead().size(); j++){
				String barcodeReceiver = receiver.getBarcodesRead().get(j);
				if(barcodeSender.equals(barcodeReceiver)){
					ArrayList<Barcode> barcodes = receiver.getMaze().getBarcodes();
					for(int k=0; k < barcodes.size(); k++){
						Barcode barcode = barcodes.get(k);
						if(barcode.getCode().equals(barcodeReceiver))
							return barcode;
					}
				}
			}
				
		}
		return null;
	}
	
	/**
	 * Een binaire barcode van de vorm abcdef (waarbij elke letter een 1 of een 0 voorstelt)
	 * wordt voorgesteld door het gehele getal 2^5a+2^4b+2^3c+2^2d+2^1e+2^0f.
	 */
	public void processBARCODE(String name, int barcode, int direction){
		String bc = Integer.toBinaryString(barcode);	
		if(direction == 3 || direction == 2){ //String reversen als ze niet in de richten zijn 
			StringBuffer reverser = new StringBuffer(bc);   //Die overeenkomt zoals nodig voor addBarcode
			bc = new String(reverser.reverse());
		}
		while (bc.length() < 8){
			bc = 0 + bc;
		}
		Ghost ghost = getGhostWithName(name);		
		Coordinate co = ghost.getMaze().getCoordinateOfLocation(ghost.getLocation());
		
		if (direction == 1 || direction == 3){ //Strings zijn al gereversed dus voor verticale richtingen
			ghost.getMaze().addBarcode(co.getX(), co.getY(), bc, 1);
		}else{
			ghost.getMaze().addBarcode(co.getX(), co.getY(), bc, 0);
		}
	}
	
	/**
	 * Processes the PACMAN command by adding the location to the list of all locations where pacman has been seen.
	 */
	public void processPACMAN(int x, int y){
		getLastPacmanLocations().add(new Location(x, y ));
	}
	
	/**
	 * Processes the CAPTURED command
	 */
	public void processCAPTURED(String name){
		getGhostWithName(name).processCAPTURED();
	}
	

	/**
	 * Processes the PLAN command
	 */
	public void processPLAN(String name, ArrayList<Location> plan){
		getGhostWithName(name).processPLAN(plan);
	}
	
	/**
	 * Processes the CANCELPLAN command.
	 */
	public void processCANCELPLAN(String name){
		getGhostWithName(name).processCANCELPLAN();
	}
	
	/**
	 * Processes the PING command
	 * @throws IOException 
	 */
	public void processPING(String name, String destination, String message) throws IOException{
		if(destination.equals("*")){
			for(Ghost ghost: getGhosts()){
				ghost.processPING(name, message);
			}
		}else{
			getGhostWithName(destination).processPING(name, message);
		}
		
	}
	
	/**
	 * Processes the PONG command
	 */
	public void processPONG(String destination, String message){
		getGhostWithName(destination).processPONG(message);
	}
	
	/**
	 * Clears all known names.
	 * Used when multiple NAME commands were received with the same name
	 */
	private void resetNames() {
		this.setGhosts(new Ghost[4]);
	}
	
	/**
	 * Returns the player with a given name.
	 * If no player with this name is registered in this game, null is returned
	 */
	private Ghost getGhostWithName(String name){
		for(Ghost ghost: getGhosts()){
			if(ghost != null && ghost.getName().equals(name)) {
				return ghost;
			}
		}
		return null;
	}

	public int getNumberOfJoins() {
		return numberOfJoins;
	}

	private void setNumberOfJoins(int numberOfJoins) {
		this.numberOfJoins = numberOfJoins;
	}

	public int getNumberOfNamesRegistered() {
		return numberOfNamesRegistered;
	}

	private void setNumberOfNamesRegistered(int numberOfNamesRegistered) {
		this.numberOfNamesRegistered = numberOfNamesRegistered;
	}
	

	public ArrayList<Location> getLastPacmanLocations() {
		return lastPacmanLocations;
	}

	private void setLastPacmanLocations(ArrayList<Location> lastPacmanLocations) {
		this.lastPacmanLocations = lastPacmanLocations;
	}
	
	public Channel getChannel() {
		return channel;
	}

	private void setChannel(Channel channel) {
		this.channel = channel;
	}
	
	public CommandParser getParser() {
		return parser;
	}

	private void setParser(CommandParser parser) {
		this.parser = parser;
	}

	public Timer getTimer() {
		return timer;
	}

	private void setTimer(Timer timer) {
		this.timer = timer;
	}

	public Pacman getPacman() {
		return pacman;
	}

	private void setPacman(Pacman pacman) {
		this.pacman = pacman;
	}
	
	public Ghost[] getGhosts() {
		return ghosts;
	}

	private void setGhosts(Ghost[] ghosts) {
		this.ghosts = ghosts;
	}
	
	public QueueingConsumer getConsumer() {
		return consumer;
	}

	private void setConsumer(QueueingConsumer consumer) {
		this.consumer = consumer;
	}

}